//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
// (c) Evgeniy Zyuzin (eaz@nm.ru), 2007
//
// Split polyline.
//

# include "StdAfx.h"
# include "SplitPolyline.h"
# include "UndoRedo.h"
# include "Label.h"
# include "Types.h"
# include "fast_floor.h"

class CSplitPolylineAction : public CEditAction {
  public:
	CSplitPolylineAction (rgn_t * _pRgn, size_t _cElement, size_t _cPoint) :
		m_pRgn (_pRgn),
		m_cElement (_cElement),
		m_cPoint (_cPoint),
		m_bWasModified ((_pRgn->dwFlags & rgn_t::fModified) != 0),
		m_pNode (NULL)
	{}

	virtual void GetName (string_t & _str) const;
	virtual cu_t CanUndo () const {return cuYes;}
	virtual bool Do ();
	virtual void Undo ();
	virtual void Redo ();

  private:
	rgn_t * const m_pRgn;
	size_t  const m_cElement;
	size_t  const m_cPoint;
	bool    const m_bWasModified;

	points_t       m_pointsOld;
	point_extras_t m_PointExtrasOld;
	rgn_t *        m_pNewRgn;
	rgnode_t *     m_pNode;

	void _ReplaceRoadInTurnRestrictions (const rgn_t * _pRoadOld, rgn_t * _pRoadNew);
};

void CSplitPolylineAction::GetName (string_t & _str) const {
	CCritSec::CLock lock (g_cs);

	_str.append ("split polyline [");
	GetHintText (* m_pRgn, _str);
	_str.append ("]");
}

static
void _SplitNumbers (WORD _w0, WORD _w1, WORD _wParity, float _fRatio, WORD & _wLeftBefore, WORD & _wLeftAfter) {
	assert (0 <= _fRatio && _fRatio <= 1);

	if (_w0 == _w1) {
		_wLeftBefore = _w0;
		_wLeftAfter  = _w0;
		return;
	}

	int N = fast_floor (_w0 + (_w1 - _w0)*_fRatio + .5f);
	assert (__min (_w0, _w1) <= N && N <= __max (_w0, _w1));

	switch (_wParity) {
		default:
		case 0:
			_wLeftBefore = 0;
			_wLeftAfter  = 0;
			break;

		case 1:
		case 2: {
			const bool bIsEven   = (N & 0x1) == 0;
			const bool bToBeEven = _wParity == 1;
			if (bIsEven == bToBeEven) {
				if (N + 1 <= __max (_w0, _w1))
					++ N;
				else if (N - 1 >= __min (_w0, _w1))
					-- N;
			}

			if (_w0 < _w1) {
				_wLeftBefore = N - 1;
				_wLeftAfter  = N + 1;
			} else {
				_wLeftBefore = N + 1;
				_wLeftAfter  = N - 1;
			}
			break;
		}

		case 3:
			_wLeftBefore = N;
			_wLeftAfter  = N;
			break;
	}
}

bool CSplitPolylineAction::Do () {
	CCritSec::CLock lock (g_cs);

	assert (m_pRgn);
	assert (m_pRgn->Kind == rkPolyline);

	assert (m_cElement < m_pRgn->elements.size ());
	rgn_element_t & element = m_pRgn->elements [m_cElement];
	const size_t cPoints = element.points.size ();

	point_extras_t * const pPointExtras = element.pExtra && ! element.pExtra->PointExtras.empty () ? & element.pExtra->PointExtras : NULL;

	// Find house numbers info to split.
	const numbers_t * pNumbersBefore = NULL;
	const numbers_t * pNumbersAfter  = NULL;
	numbers_t NumbersSplit;
	if (pPointExtras && HasNumbers (* pPointExtras)) {
		numbers_t & NumbersAtSplitPoint = (* pPointExtras) [m_cPoint].Numbers;
		if (! NumbersAtSplitPoint.bHasNumbers) {
			const size_t cPoints = (* pPointExtras).size ();

			size_t cAfter = -1;
			int n;
			for (n = m_cPoint + 1; n < (int) cPoints; ++ n) {
				numbers_t & Numbers = (* pPointExtras) [n].Numbers;
				if (Numbers.bHasNumbers) {
					pNumbersAfter = & Numbers;
					cAfter = n;
					break;
				}
			}

			size_t cBefore = -1;
			for (n = m_cPoint - 1; n >= 0; -- n) {
				numbers_t & Numbers = (* pPointExtras) [n].Numbers;
				if (Numbers.bHasNumbers) {
					pNumbersBefore = & Numbers;
					cBefore = n;
					break;
				}
			}

			// Calculate house numbers at split point
			if (pNumbersAfter && pNumbersBefore) {
				const float fCosY = ::cos (element.points [0].y);
				float len_total = 0, len = 0;
				for (size_t c = cBefore; c < cAfter; ++ c) {
					const point_t & pt0 = element.points [c];
					const point_t & pt1 = element.points [c + 1];
					const float l = ::hypot ((pt1.x - pt0.x)*fCosY, pt1.y - pt0.y);
					len_total += l;
					if (c < m_cPoint)
						len += l;
				}

				WORD wLeftBefore,  wLeftAfter;
				_SplitNumbers (pNumbersBefore->wLeftAfter,  pNumbersAfter->wLeftBefore,  pNumbersBefore->wLeftParity,  len/len_total, wLeftBefore,  wLeftAfter);
				WORD wRightBefore, wRightAfter;
				_SplitNumbers (pNumbersBefore->wRightAfter, pNumbersAfter->wRightBefore, pNumbersBefore->wRightParity, len/len_total, wRightBefore, wRightAfter);

				NumbersSplit.wLeftBefore     = wLeftBefore;
				NumbersSplit.wLeftAfter      = wLeftAfter;
				NumbersSplit.wLeftParity     = pNumbersBefore->wLeftParity;
				NumbersSplit.pZipLeftAfter   = pNumbersBefore->pZipLeftAfter;
				NumbersSplit.pCityLeftAfter  = pNumbersBefore->pCityLeftAfter;

				NumbersSplit.wRightBefore    = wRightBefore;
				NumbersSplit.wRightAfter     = wRightAfter;
				NumbersSplit.wRightParity    = pNumbersBefore->wRightParity;
				NumbersSplit.pZipRightAfter  = pNumbersBefore->pZipRightAfter;
				NumbersSplit.pCityRightAfter = pNumbersBefore->pCityRightAfter;

				NumbersSplit.bHasNumbers     = true;
			}
		}
	}

	//
	// Trim existing points.
	//

	m_pointsOld = element.points;
	element.points.assign (m_pointsOld.begin (), m_pointsOld.begin () + m_cPoint + 1);

	assert (m_pNode == NULL);
	if (pPointExtras) {
		m_PointExtrasOld = * pPointExtras;

		element.pExtra->PointExtras.assign (m_PointExtrasOld.begin (), m_PointExtrasOld.begin () + m_cPoint + 1);

		m_pNode = (* pPointExtras) [m_cPoint].GetRGNode ();
		if (m_pNode == NULL && HasRGNodes (* pPointExtras)) {
			// Create node at the split point.
			m_pNode = & g_map.CreateNode ();
			(* pPointExtras) [m_cPoint].SetRGNode (m_pNode, true);
		}

		numbers_t & Numbers = (* pPointExtras) [m_cPoint].Numbers;
		if (Numbers.bHasNumbers) {
			Numbers.wLeftParity  = 0;
			Numbers.wRightParity = 0;
			Numbers.wLeftAfter   = 0;
			Numbers.wRightAfter  = 0;
			Numbers.pCityLeftAfter  = NULL;
			Numbers.pCityRightAfter = NULL;
			Numbers.pZipLeftAfter   = NULL;
			Numbers.pZipRightAfter  = NULL;
		} else {
			if (NumbersSplit.bHasNumbers) {
				Numbers.bHasNumbers = true;
				Numbers.wLeftBefore  = NumbersSplit.wLeftBefore;
				Numbers.wRightBefore = NumbersSplit.wRightBefore;
			}
		}
	}

	m_pRgn->RefreshAttributes (g_map);
	m_pRgn->dwFlags |= rgn_t::fModified;

	//
	// Prepare rest of points.
	//

	points_t pointsNew (m_pointsOld.begin () + m_cPoint, m_pointsOld.end ());
	point_extras_t PointExtrasNew;
	if (pPointExtras) {
		point_extras_t PointExtrasNew2 (m_PointExtrasOld.begin () + m_cPoint, m_PointExtrasOld.end ());
		PointExtrasNew.swap (PointExtrasNew2);

		if (m_pNode) {
			if (! PointExtrasNew.empty () && PointExtrasNew [0].GetRGNode () == NULL)
				PointExtrasNew [0].SetRGNode (m_pNode, true);
			else
				++ m_pNode->btPolylines;
		}

		if (! PointExtrasNew.empty ()) {
			numbers_t & Numbers = PointExtrasNew [0].Numbers;
			if (! Numbers.bHasNumbers && NumbersSplit.bHasNumbers)
				Numbers = NumbersSplit;
			Numbers.wLeftBefore  = 0;
			Numbers.wRightBefore = 0;
		}
	}

	//
	// Create new object for the rest of points.
	//
	m_pNewRgn = & g_map.CreateRgn ((RGN_KIND) m_pRgn->Kind);

	// Initialize the object.
	m_pNewRgn->dwFlags    |= rgn_t::fModified;

	m_pNewRgn->strLabel    = m_pRgn->strLabel;
	m_pNewRgn->strComment  = m_pRgn->strComment;
	m_pNewRgn->uchEndLevel = m_pRgn->uchEndLevel;
	if (m_pRgn->pExtra)
		m_pNewRgn->pExtra  = m_pRgn->pExtra;

	m_pNewRgn->SetType (m_pRgn->pTypeDesc, m_pRgn->GetType (), m_pRgn->Kind);

	// Create the element of the rgn.
	m_pNewRgn->elements.push_back (rgn_element_t ());
	rgn_element_t & elementNew = m_pNewRgn->elements.back ();

	// Initialize the element of rgn.
	elementNew.cFileOffset = -1;
	elementNew.cLevel      = element.cLevel;
	elementNew.points.swap (pointsNew);

	// RG nodes.
	if (pPointExtras) {
		elementNew.pExtra->PointExtras.swap (PointExtrasNew);
		_ReplaceRoadInTurnRestrictions (m_pRgn, m_pNewRgn);
	}

	// DirIndicator.
	if (m_pRgn->dwFlags & rgn_t::fDirIndicator)
		m_pNewRgn->dwFlags |= rgn_t::fDirIndicator;

	// DontFind.
	if (m_pRgn->dwFlags & rgn_t::fDontFind)
		m_pNewRgn->dwFlags |= rgn_t::fDontFind;

	m_pNewRgn->RefreshAttributes (g_map);

	return true;
}

void CSplitPolylineAction::Undo () {
	CCritSec::CLock lock (g_cs);

	assert (m_cElement < m_pRgn->elements.size ());
	rgn_element_t & element = m_pRgn->elements [m_cElement];

	element.points.swap (m_pointsOld);
	if (element.pExtra || ! m_PointExtrasOld.empty ())
		element.pExtra->PointExtras.swap (m_PointExtrasOld);

	m_pRgn->RefreshAttributes (g_map);
	if (! m_bWasModified)
		m_pRgn->dwFlags &= ~rgn_t::fModified;

	_ReplaceRoadInTurnRestrictions (m_pNewRgn, m_pRgn);

	assert (m_pNewRgn);
	m_pNewRgn->Delete (g_map);

	if (m_pNode)
		-- m_pNode->btPolylines;
}

void CSplitPolylineAction::Redo () {
	CCritSec::CLock lock (g_cs);

	assert (m_cElement < m_pRgn->elements.size ());
	rgn_element_t & element = m_pRgn->elements [m_cElement];

	element.points.swap (m_pointsOld);
	if (element.pExtra || ! m_PointExtrasOld.empty ())
		element.pExtra->PointExtras.swap (m_PointExtrasOld);

	m_pRgn->RefreshAttributes (g_map);
	m_pRgn->dwFlags |= rgn_t::fModified;

	_ReplaceRoadInTurnRestrictions (m_pRgn, m_pNewRgn);

	assert (m_pNewRgn);
	m_pNewRgn->Undelete (g_map);

	if (m_pNode)
		++ m_pNode->btPolylines;
}

void CSplitPolylineAction::_ReplaceRoadInTurnRestrictions (const rgn_t * _pRoadOld, rgn_t * _pRoadNew) {
	assert (m_pNewRgn && ! m_pNewRgn->elements.empty ());
	point_extras_t & PointExtras = m_pNewRgn->elements [0].pExtra->PointExtras;

	const size_t cPointExtras = PointExtras.size ();
	for (size_t c = 0; c < cPointExtras; ++ c) {
		rgnode_t * const pNode = PointExtras [c].GetRGNode ();
		if (! pNode)
			continue;

		typedef std::pair<rgrestrictions_t::iterator, rgrestrictions_t::iterator> rr_t;
		const rr_t rr = g_map.rgrestrictions.equal_range (pNode);
		for (rgrestrictions_t::iterator ir = rr.first; ir != rr.second; ++ ir) {
			rgrestriction_t & r = ir->second;

			if (c != 0) {
				if (r.pFromRoad == _pRoadOld)
					r.pFromRoad = _pRoadNew;
				if (r.pToRoad   == _pRoadOld)
					r.pToRoad   = _pRoadNew;
			} else {
				if (r.pFromRoad == _pRoadOld && r.bFromDirection == 0)
					r.pFromRoad = _pRoadNew;
				if (r.pToRoad   == _pRoadOld && r.bToDirection   != 0)
					r.pToRoad   = _pRoadNew;
			}
		}
	}
}

//////////////////////////////////////////

void SplitPolyline (rgn_t * _pRgn, size_t _cElement, size_t _cPoint) {
	assert (_pRgn);
	assert (_pRgn->Kind == rkPolyline);
	assert (! _pRgn->IsWTR ());

	assert (_cElement < _pRgn->elements.size ());
	assert (_cPoint < _pRgn->elements [_cElement].points.size ());

	PerformEditAction (new CSplitPolylineAction (_pRgn, _cElement, _cPoint));
}